﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")]
	public sealed class IRBasicAccessPath : IComparable<IRBasicAccessPath>, IEquatable<IRBasicAccessPath>
	{
		/// <summary>
		/// The singleton representation for the empty access path, reduces GC and copy pressure.
		/// </summary>
		public static IRBasicAccessPath EmptyAccessPath = new IRBasicAccessPath();

		private int m_compareid;

		/// <summary>
		/// this is just for creating our global empty path.
		/// </summary>
		private IRBasicAccessPath()
		{
			this.m_accep = new IRMemberField[] { };
			this.m_tail = null;
			this.m_extendpost = new Dictionary<IRMemberField, IRBasicAccessPath>();

			this.m_compareid = -1;
		}

		/// <summary>
		/// Array of accesses [f1, f2, ..., fk].
		/// </summary>
		private readonly IRMemberField[] m_accep;

		/// <summary>
		/// The access path that is the tail of this [f2, ..., fk].
		/// </summary>
		private IRBasicAccessPath m_tail;

		/// <summary>
		/// All possible extensions of this mapped on the field.
		/// fj -> [f1, ..., fk, fj]
		/// </summary>
		private readonly Dictionary<IRMemberField, IRBasicAccessPath> m_extendpost;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant((this.m_accep.Length > 0) | (EmptyAccessPath == null) | (this == EmptyAccessPath));
			Contract.Invariant(this.m_accep != null);
			Contract.Invariant(this.m_extendpost != null);
		}

		/// <summary>
		/// The access paths are only for subindexing within a structure so the target type of all the fields (except for the last one) 
		/// should all be non-pointer typed.
		/// Return true if this is the case for a given array.
		/// </summary>
		[Pure]
		private static bool AccessPathIsNonPointerExceptForPossiblyLastAndChainable(IRMemberField[] accp)
		{
			for(int i = 0; i < accp.Length - 1; ++i)
			{
				if(accp[i].FieldType is IRPtrType)
					return false;
			}

			if(accp.Length != 0)
			{
				IRType tt = accp[0].FieldType;
				for(int i = 1; i < accp.Length; ++i)
				{
					if(!tt.TypeDescription.AllVisibleFields.Contains(accp[i]))
						return false;

					tt = accp[i].FieldType;
				}
			}

			return true;
		}

		/// <summary>
		/// Create the extentions of the accesspath (or single field) with the other field.
		/// </summary>
		internal IRBasicAccessPath(IRMemberField[] accep)
			: base()
		{
			Contract.Requires((accep != null) & (accep.Length > 0));
			Contract.Assert(AccessPathIsNonPointerExceptForPossiblyLastAndChainable(accep), "We have an ill defined access path");

			this.m_accep = accep;
			this.m_tail = null;
			this.m_extendpost = new Dictionary<IRMemberField, IRBasicAccessPath>();

			this.m_compareid = -1;
		}

		/// <summary>
		/// Return true if this access path is a prefix of other.
		/// </summary>
		[Pure]
		public bool IsAccessPathPrefixOf(IRBasicAccessPath other)
		{
			if(this.m_accep.Length > other.m_accep.Length)
				return false;

			for(int i = 0; i < this.m_accep.Length; ++i)
			{
				if(this.m_accep[i] != other.m_accep[i])
					return false;
			}

			return true;
		}

		public bool IsEmptyAccessPath
		{ get { return this == EmptyAccessPath; } }

		/// <summary>
		/// True if all the index steps are NOT fully determined (i.e., it is a container field).
		/// </summary>
		public bool AccessPathHasSummaryLocation
		{ get { return (this != EmptyAccessPath && this.m_accep[0].IsSummaryField); } }

		public IRMemberField PathFirst
		{ get { return this.m_accep[0]; } }

		public IRMemberField PathLast
		{ get { return this.m_accep[this.m_accep.Length - 1]; } }

		public IRMemberField[] Path
		{ get { return this.m_accep; } }

		public IRBasicAccessPath Tail
		{
			get { return this.m_tail; }
			set { this.m_tail = value; }
		}

		public Dictionary<IRMemberField, IRBasicAccessPath> PostExtensionMap
		{ get { return this.m_extendpost; } }

		public override string ToString()
		{
			if(this.m_accep.Length == 0)
				return "*";
			else if(this.m_accep.Length == 1)
				return "*" + this.m_accep[0].ToString();
			else
			{
				StringBuilder sb = new StringBuilder("*");
				for(int i = 0; i < this.m_accep.Length; ++i)
				{
					if(i != 0)
						sb.Append(".");
					sb.Append(this.m_accep[i].ToString());
				}

				return sb.ToString();
			}
		}

		public string NiceDisplayString()
		{
			if(this.m_accep.Length == 0)
				return "*";
			else if(this.m_accep.Length == 1)
				return "*" + this.m_accep[0].FieldName;
			else
			{
				StringBuilder sb = new StringBuilder("*");
				for(int i = 0; i < this.m_accep.Length; ++i)
				{
					if(i != 0)
						sb.Append(".");
					sb.Append(this.m_accep[i].FieldName);
				}

				return sb.ToString();
			}
		}

		public string FieldOnlyDisplayString()
		{
			if(this.m_accep.Length == 1)
				return this.m_accep[0].FieldName;
			else
			{
				StringBuilder sb = new StringBuilder();
				for(int i = 0; i < this.m_accep.Length; ++i)
				{
					if(i != 0)
						sb.Append(".");
					sb.Append(this.m_accep[i].FieldName);
				}

				return sb.ToString();
			}
		}

		public static void SetAccessPathCompareInfo(ICollection<IRBasicAccessPath> allpaths)
		{
			List<IRBasicAccessPath> spl = new List<IRBasicAccessPath>(allpaths);
			spl.Sort((IRBasicAccessPath bap1, IRBasicAccessPath bap2) => BAPLexoCompare(bap1, bap2));

			for(int i = 0; i < spl.Count; ++i)
				spl[i].m_compareid = i;
		}

		private static int BAPLexoCompare(IRBasicAccessPath bap1, IRBasicAccessPath bap2)
		{
			if(bap1.Path.Length != bap2.Path.Length)
				return bap1.Path.Length - bap2.Path.Length;

			for(int i = 0; i < bap1.Path.Length; ++i)
			{
				int ppc = bap1.Path[i].CompareTo(bap2.Path[i]);
				if(ppc != 0)
					return ppc;
			}

			return 0;
		}

		public int CompareTo(IRBasicAccessPath other)
		{
			Contract.Assert(other != null);
			Contract.Assert((this.m_compareid != -1) & (other.m_compareid != -1));

			return this.m_compareid - other.m_compareid;
		}

		public bool Equals(IRBasicAccessPath other)
		{
			Contract.Assert(other != null);
			Contract.Assert((this.m_compareid != -1) & (other.m_compareid != -1));

			return this.m_compareid == other.m_compareid;
		}

		public override bool Equals(object obj)
		{ return (obj is IRBasicAccessPath) && this.Equals((IRBasicAccessPath)obj); }

		public override int GetHashCode()
		{ return this.m_compareid; }
	}

	public sealed class IRBasicAccessPathComparer : IEqualityComparer<IRMemberField[]>
	{
		public bool Equals(IRMemberField[] x, IRMemberField[] y)
		{
			if(x.Length != y.Length)
				return false;
			else
			{
				for(int i = 0; i < x.Length; ++i)
				{
					if(x[i] != y[i])
						return false;
				}

				return true;
			}
		}

		public int GetHashCode(IRMemberField[] obj)
		{
			return obj.Length;
		}
	}
}
